package com.ant.backstage.controller;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import com.ant.backstage.config.jwt.JwtToken;
import com.ant.backstage.facade.ApiReturn;
import com.ant.backstage.facade.code.ApiReturnCode;
import com.ant.backstage.facade.dto.CheckVerifyCodeDTO;
import com.ant.backstage.facade.dto.UserDTO;
import com.ant.backstage.facade.enums.UserStatusEnum;
import com.ant.backstage.facade.exception.BackstageBusinessException;
import com.ant.backstage.facade.model.User;
import com.ant.backstage.facade.request.registerLogin.FindPwdRequst;
import com.ant.backstage.facade.request.registerLogin.UserLoginRequest;
import com.ant.backstage.facade.request.registerLogin.UserRegisterRequest;
import com.ant.backstage.facade.validation.UserConstant;
import com.ant.backstage.service.RegisterAndLoginService;
import com.ant.backstage.service.UserService;
import com.ant.backstage.utils.RedisUtil;
import io.swagger.annotations.ApiOperation;
import ma.glasnost.orika.MapperFacade;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import java.util.Optional;

/**
 * @author Administrator
 * @date 2021/12/23 12:32
 **/
@RestController
@RequestMapping("/api/user")
public class RegisterAndLogin {

    private static Logger logger = LogManager.getLogger(indexController.class);

    @Resource
    RegisterAndLoginService registerAndLoginService;
    @Resource
    UserService userService;
    @Resource
    private MapperFacade mapperFacade;
    @Resource
    RedisUtil redisUtil;
    @Resource
    RedisTemplate redisTemplate;

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    @ApiOperation(value = "用户注册", response = ApiReturn.class)
    public ApiReturn userRegister(@RequestBody UserRegisterRequest request) {
        logger.info("用户注册入参 request={}", JSONObject.toJSONString(request));
        ApiReturn apiReturn = new ApiReturn();
        try {
            User user = mapperFacade.map(request, User.class);
            //校验账号的唯一性
            User userdb = registerAndLoginService.findByUsername(user.getUname());
            if (Optional.ofNullable(userdb).isPresent()) {
                logger.error("当前用户名已存在,注册失败 request={}", JSONObject.toJSONString(request));
                apiReturn.setCode(ApiReturnCode.USERNAME_EXISTED_ERROR.getCode());
                apiReturn.setMsg(ApiReturnCode.USERNAME_EXISTED_ERROR.getMessage());
                apiReturn.setData(request);
                return apiReturn;
            }
            apiReturn = registerAndLoginService.register(request);
            return apiReturn;
        } catch (Exception e) {
            logger.error("用户注册异常 e={},request={}", e, JSONObject.toJSONString(request));
            throw new BackstageBusinessException(ApiReturnCode.USER_REGISTER_ERROR.getMessage(), ApiReturnCode.USER_REGISTER_ERROR.getCode());
        }
    }

    //登录认证授权
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ApiOperation(value = "登录认证授权", response = ApiReturn.class)
    public ApiReturn login(@RequestBody UserLoginRequest request) {
        logger.info("登录授权接口入参 request={}", JSONObject.toJSONString(request));
        ApiReturn apiReturn = new ApiReturn();
        if (!Optional.ofNullable(request.getUname()).isPresent() || !Optional.ofNullable(request.getPwd()).isPresent()) {
            apiReturn.setCode(ApiReturnCode.PARAMS_ERROR.getCode());
            apiReturn.setMsg(ApiReturnCode.PARAMS_ERROR.getMessage());
            return apiReturn;
        }

        User user = registerAndLoginService.login(request.getUname(), request.getPwd());
        if (!Optional.ofNullable(user).isPresent()) {
            logger.error("登录失败,用户信息不存在 request={},user={}",
                    JSONObject.toJSONString(request), JSONObject.toJSONString(user));
            apiReturn.setCode(ApiReturnCode.USERNAME_LOGIN_ERROR.getCode());
            apiReturn.setMsg(ApiReturnCode.USERNAME_LOGIN_ERROR.getMessage());
            return apiReturn;
        }
        Subject subject = SecurityUtils.getSubject();
        if (subject.getPrincipal() instanceof UserDTO) {
            //说明已经登录过 避免重复登录问题
            UserDTO userDTO = (UserDTO) subject.getPrincipal();
            boolean key = redisUtil.hasKey(userDTO.getUid() + UserConstant.USER_TOKEN_SUFFIX);
            if (key) {
                //redis 序列化方式  在特定情况下使用特定序列化方式
                redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
                SimpleAuthenticationInfo info = (SimpleAuthenticationInfo) redisTemplate.opsForValue().get(userDTO.getUid() + UserConstant.USER_TOKEN_SUFFIX);
                redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());

                UserDTO userPo = (UserDTO) info.getPrincipals().getPrimaryPrincipal();
                if (userPo.getToken().equals(userDTO.getToken())) {
                    logger.warn("用户重复登录 request={},user={}",
                            JSONObject.toJSONString(request), JSONObject.toJSONString(user));
                    apiReturn.setCode(ApiReturnCode.SUCCESSFUL.getCode());
                    apiReturn.setMsg(ApiReturnCode.USERNAME_LOGINED.getMessage());
                    return apiReturn;
                }
            }
        }
        try {
            // 由于jwt是正确的，所以这里的login必定成功。此举是为了将认证信息缓存起来
            subject.login(new JwtToken(null, user.getUid() + UserConstant.USER_TOKEN_SUFFIX));
        } catch (AuthenticationException e) {
            logger.error("用户登录异常");
            boolean b = redisUtil.hasKey(user.getUid() + UserConstant.USER_TOKEN_SUFFIX);
            if (b) {
                redisUtil.del(user.getUid() + UserConstant.USER_TOKEN_SUFFIX);
            }
            apiReturn.setApiReturnCode(ApiReturnCode.HTTP_ERROR);
            return apiReturn;
        }
        //Session session = subject.getSession(); 进行session操作 sessionid已经存入缓存，可进行操作
        // 为什么登录不使用UsernamePasswordToken和定义专门的LoginRealm（Service层的逻辑）来处理UsernamePasswordToken？
        // 由于密码登录只用一次，成功之后都凭借jwt令牌来访问,经过LoginRealm后所缓存起来的认证信息之后多不会被用到
        apiReturn.setApiReturnCode(ApiReturnCode.SUCCESSFUL);
        return apiReturn;

    }

    /**
     * 退出登录
     * 销毁主体的认证信息
     */
    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    @ApiOperation(value = "退出", response = ApiReturn.class)
    public ApiReturn logout() {
        ApiReturn apiReturn = new ApiReturn();
        Subject subject = SecurityUtils.getSubject();
        UserDTO userDTO = (UserDTO) subject.getPrincipal();
        redisUtil.delPrefixStr(userDTO.getUid());
        redisUtil.del(userDTO.getToken());
        subject.logout();
        userService.updateUserStatus(UserStatusEnum.USER_STATE_LOGINGOUT.getCode(), userDTO.getUid());
        apiReturn.setApiReturnCode(ApiReturnCode.SUCCESSFUL);
        return apiReturn;
    }

    //找回密码
    @RequestMapping(value = "/updatePwd", method = RequestMethod.POST)
    @ApiOperation(value = "找回密码", response = ApiReturn.class)
    public ApiReturn findPwd(@RequestBody FindPwdRequst findPwdRequst, HttpServletRequest request) {
        ApiReturn apiReturn = new ApiReturn();
        String smstoken = request.getHeader("SMS");
        CheckVerifyCodeDTO checkVerifyCodeDTO = (CheckVerifyCodeDTO) redisUtil.get(smstoken);
        //校验参数
        if (!checkVerifyCodeDTO.getContactType().equals(findPwdRequst.getCheckType())
                || !checkVerifyCodeDTO.getVerifyCode().equals(findPwdRequst.getVerifyCode())
                || !checkVerifyCodeDTO.getUname().equals(findPwdRequst.getUname())
                || !checkVerifyCodeDTO.getUserContact().equals(findPwdRequst.getUserContact())
                || !findPwdRequst.getNewPwd().equals(findPwdRequst.getConfirmPwd())
        ) {
            apiReturn.setCode(ApiReturnCode.PARAMS_ERROR.getCode());
            apiReturn.setMsg(ApiReturnCode.PARAMS_ERROR.getMessage());
            return apiReturn;
        }
        apiReturn = registerAndLoginService.findPwd(apiReturn, findPwdRequst);
        return apiReturn;
    }

    //shiro放行
    @ApiOperation(value = "获取用户联系方式", response = ApiReturn.class)
    @RequestMapping(value = "/selectUserContact", method = RequestMethod.GET)
    public ApiReturn selectUserContact(@RequestParam @NotNull(message = "参数不能为空") String uname,
                                       @RequestParam @NotNull(message = "参数不能为空") String contactType) {
        logger.info("获取用户联系方式入参 uname={}", JSONObject.toJSONString(uname));
        ApiReturn apiReturn = userService.selectUserContact(uname, contactType);
        return apiReturn;
    }

    //shiro放行
    @ApiOperation(value = "用户名是否已存在", response = ApiReturn.class)
    @RequestMapping(value = "/selectUserName", method = RequestMethod.GET)
    public ApiReturn selectUserName(@RequestParam @NotNull(message = "参数不能为空") String uname) {
        logger.info("用户名是否已存在 uname={}", JSONObject.toJSONString(uname));
        ApiReturn apiReturn = new ApiReturn();
        User user = registerAndLoginService.findByUsername(uname);
        if (Optional.ofNullable(user).isPresent()) {
            apiReturn.setCode(ApiReturnCode.USERNAME_EXISTED_ERROR.getCode());
            apiReturn.setMsg(ApiReturnCode.USERNAME_EXISTED_ERROR.getMessage());
        } else {
            apiReturn.setApiReturnCode(ApiReturnCode.SUCCESSFUL);
        }
        return apiReturn;
    }

    //登录/授权测试接口
    @RequestMapping(value = "/info", method = RequestMethod.GET)
    @RequiresPermissions(value = {"ALL::LOGIN", "ADMIN::POWER"}, logical = Logical.OR)
    @ApiOperation(value = "shiro测试接口", response = ApiReturn.class)
    public ResponseEntity<String> indexsss() {
        return ResponseEntity.ok("用户信息！这个接口需要携带有效的token才能访问");
    }
}
